﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.SqlServer.Dts.Pipeline;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using System;

using DatabaseGroup.SSIS.SourceAdapters;
using DatabaseGroup.SSIS.SourceAdapters.Parser;
using Microsoft.SqlServer.Dts.Runtime.Wrapper;
using System.Collections.Generic;

namespace DatabaseGroup.UnitTests.SSIS.SourceAdapters
{
    [TestClass()]
    public class BufferSinkTest
    {
        private TestContext _testContextInstance;

        public TestContext TestContext
        {
            get
            {
                return _testContextInstance;
            }
            set
            {
                _testContextInstance = value;
            }
        }

        [TestMethod()]
        [ExpectedException(typeof(ArgumentNullException), "Expected ArgumentNullException to be thrown when ctor called with null args")]
        public void BufferSink_ConstructorWithNullArgs_ThrowArgumentNullException()
        {
            BufferSink target = new BufferSink(null, null, false, true, false);
        }

        [TestMethod()]
        [ExpectedException(typeof(ArgumentNullException), "Expected ArgumentNullException to be thrown when ctor called with null 2nd arg")]
        public void BufferSink_ConstructorWithNull2ndArg_ThrowArgumentNullException()
        {
            ComponentBufferServiceTestImpl bufferService = new ComponentBufferServiceTestImpl(new string[0], false, false);
            BufferSink target = new BufferSink(bufferService, null, false, true, false);
        }

        [TestMethod()]
        public void CurrentRowCount_DefaultValue_ReturnExpectedDefaultZeroValue()
        {
            IDTSOutput100 output = new OutputTestImpl();
            IComponentBufferService bufferService = new ComponentBufferServiceTestImpl(new string[0], false, false);
            BufferSink target = new BufferSink(bufferService, output, false, true, false);
            Assert.AreEqual<Int64>(0, target.CurrentRowCount, "Expected default row count of zero was not returned");
        }

        [TestMethod()]
        public void AddRow_AddSingleRow_ReturnExpectedRowCountsAndData()
        {
            OutputTestImpl output = null;
            ComponentBufferServiceTestImpl bufferService = null;
            GenerateOutputColumns(new string[] { "A", "B", "C", "D" }, false, false, DTSRowDisposition.RD_FailComponent, DTSRowDisposition.RD_FailComponent, out output, out bufferService);
            BufferSink target = new BufferSink(bufferService, output, false, true, false);
            SetOutputColumns(target, bufferService.ColumnCount);
            string[] data = new string[] { "1", "2", "3", "4" };
            RowData rowData = GenerateRowData(data);
            target.AddRow(rowData);

            Assert.AreEqual<Int64>(1, target.CurrentRowCount, "Expected current row count to be 1 after processing a single row");
            Assert.AreEqual<int>(1, bufferService.RowCount, "Expected standard row count to be 1 after processing a single row");
            Assert.AreEqual<int>(0, bufferService.ErrorRowCount, "Expected error row count to be 0 after processing 1 valid row");
            Assert.AreEqual<int>(0, bufferService.LogRowCount, "Expected log row count to be 0 after processing 1 valid row");

            VerifyAddedRowData(bufferService, data);
        }

        [TestMethod()]
        public void AddRow_HandleSingleRowWithNullData_ReturnExpectedRowCountsAndData()
        {
            OutputTestImpl output = null;
            ComponentBufferServiceTestImpl bufferService = null;
            GenerateOutputColumns(new string[] { "A", "B", "C", "D" }, false, false, DTSRowDisposition.RD_FailComponent, DTSRowDisposition.RD_FailComponent, out output, out bufferService);
            BufferSink target = new BufferSink(bufferService, output, true, true, false);
            SetOutputColumns(target, bufferService.ColumnCount);
            string[] data = new string[] { "1", "", "3", "4" };

            RowData rowData = GenerateRowData(data);
            target.AddRow(rowData);

            Assert.AreEqual<Int64>(1, target.CurrentRowCount, "Expected current row count to be 1 after processing a single row");
            Assert.AreEqual<int>(1, bufferService.RowCount, "Expected standard row count to be 1 after processing a single row");
            Assert.AreEqual<int>(0, bufferService.ErrorRowCount, "Expected error row count to be 0 after processing 1 valid row");
            Assert.AreEqual<int>(0, bufferService.LogRowCount, "Expected log row count to be 0 after processing 1 valid row");

            VerifyAddedRowData(bufferService, new object[] { "1", null, "3", "4" });
        }

        [TestMethod()]
        public void AddRow_HandleRowWithLessColumns_ReturnExpectedRowCountsAndData()
        {
            OutputTestImpl output = null;
            ComponentBufferServiceTestImpl bufferService = null;
            GenerateOutputColumns(new string[] { "A", "B", "C", "D" }, false, false, DTSRowDisposition.RD_FailComponent, DTSRowDisposition.RD_FailComponent, out output, out bufferService);
            BufferSink target = new BufferSink(bufferService, output, true, true, false);
            SetOutputColumns(target, bufferService.ColumnCount);
            string[] data = new string[] { "1", "", "3" };

            output.TruncationRowDisposition = DTSRowDisposition.RD_RedirectRow;

            RowData rowData = GenerateRowData(data);
            target.AddRow(rowData);

            Assert.AreEqual<Int64>(1, target.CurrentRowCount, "Expected current row count to be 1 after processing a single row");
            Assert.AreEqual<int>(0, bufferService.RowCount, "Expected standard row count to be 0 after processing a single row");
            Assert.AreEqual<int>(1, bufferService.ErrorRowCount, "Expected error row count to be 1 after processing 1 valid row");
            Assert.AreEqual<int>(0, bufferService.LogRowCount, "Expected log row count to be 0 after processing 1 valid row");

            VerifyAddedRowData(bufferService, new object[] { "1", null, "3" });
        }

        [TestMethod()]
        [ExpectedException(typeof(BufferSinkException), "Expected BufferSinkException to be thrown when called with row data containing too many columns")]
        public void AddRow_HandleRowWithTooManyColumns_ThrowBufferSinkException()
        {
            OutputTestImpl output = null;
            ComponentBufferServiceTestImpl bufferService = null;
            GenerateOutputColumns(new string[] { "A", "B", "C", "D" }, true, false, DTSRowDisposition.RD_FailComponent, DTSRowDisposition.RD_FailComponent, out output, out bufferService);
            BufferSink target = new BufferSink(bufferService, output, true, true, false);
            string[] data = new string[] { "1", "", "3", "4", "5", "6" };

            RowData rowData = GenerateRowData(data);
            try
            {
                target.AddRow(rowData);
            }
            catch (BufferSinkException)
            {
                Assert.AreEqual<Int64>(1, target.CurrentRowCount, "Expected current row count to be 1 after processing a single row");
                Assert.AreEqual<int>(0, bufferService.RowCount, "Expected standard row count to be 0 after processing a single invalid row");
                Assert.AreEqual<int>(0, bufferService.ErrorRowCount, "Expected error row count to be 0 after processing a single invalid row with no error redirection");
                Assert.AreEqual<int>(0, bufferService.LogRowCount, "Expected log row count to be 0 after processing a single invalid row with no error redirection");
                throw;
            }
        }

        [TestMethod()]
        public void AddRow_HandleRowWithTooManyColumnsWithTruncationRedirect_ReturnExpectedRowCounts()
        {
            OutputTestImpl output = null;
            ComponentBufferServiceTestImpl bufferService = null;
            GenerateOutputColumns(new string[] { "A", "B", "C", "D" }, true, false, DTSRowDisposition.RD_FailComponent, DTSRowDisposition.RD_FailComponent, out output, out bufferService);
            output.TruncationRowDisposition = DTSRowDisposition.RD_RedirectRow;
            BufferSink target = new BufferSink(bufferService, output, true, true, false);
            string[] data = new string[] { "1", "", "3", "4", "5", "6" };

            RowData rowData = GenerateRowData(data);
            target.AddRow(rowData);
            Assert.AreEqual<Int64>(1, target.CurrentRowCount, "Expected current row count to be 1 after processing a single row");
            Assert.AreEqual<int>(0, bufferService.RowCount, "Expected standard row count to be 0 after processing a single invalid row");
            Assert.AreEqual<int>(1, bufferService.ErrorRowCount, "Expected error row count to be 1 after processing a single invalid row with error redirection");
            Assert.AreEqual<int>(0, bufferService.LogRowCount, "Expected log row count to be 0 after processing a single invalid row with error redirection");
        }

        [TestMethod()]
        public void AddRow_HandleRowWithTooManyColumnsWithTruncationIgnore_ReturnExpectedRowCounts()
        {
            OutputTestImpl output = null;
            ComponentBufferServiceTestImpl bufferService = null;
            GenerateOutputColumns(new string[] { "A", "B", "C", "D" }, true, true, DTSRowDisposition.RD_FailComponent, DTSRowDisposition.RD_FailComponent, out output, out bufferService);
            output.TruncationRowDisposition = DTSRowDisposition.RD_IgnoreFailure;
            BufferSink target = new BufferSink(bufferService, output, true, true, false);
            string[] data = new string[] { "1", "", "3", "4", "5", "6" };
            SetOutputColumns(target, bufferService.ColumnCount);

            RowData rowData = GenerateRowData(data);
            target.AddRow(rowData);
            Assert.AreEqual<Int64>(1, target.CurrentRowCount, "Expected current row count to be 1 after processing a single row");
            Assert.AreEqual<int>(1, bufferService.RowCount, "Expected standard row count to be 1 after processing a single invalid row");
            Assert.AreEqual<int>(0, bufferService.ErrorRowCount, "Expected error row count to be 0 after processing a single invalid row with truncation ignore");
            Assert.AreEqual<int>(1, bufferService.LogRowCount, "Expected log row count to be 1 after processing a single invalid row with truncation ignore");
        }

        [TestMethod()]
        [ExpectedException(typeof(BufferSinkException), "Expected BufferSinkException to be thrown via AddRow because underlying bufferService was set to throw it")]
        public void AddRow_HandleRowAndForceException_ThrowBufferSinkException()
        {
            OutputTestImpl output = null;
            ComponentBufferServiceTestImpl bufferService = null;
            GenerateOutputColumns(new string[] { "A", "B", "C", "D" }, true, false, DTSRowDisposition.RD_FailComponent, DTSRowDisposition.RD_FailComponent, out output, out bufferService);
            bufferService.ExceptionToFire = new System.Runtime.InteropServices.COMException();
            BufferSink target = new BufferSink(bufferService, output, true, false, false);
            SetOutputColumns(target, bufferService.ColumnCount);
            string[] data = new string[] { "1", "", "3", "4" };

            RowData rowData = GenerateRowData(data);
            try
            {
                target.AddRow(rowData);
            }
            catch
            {
                Assert.AreEqual<Int64>(1, target.CurrentRowCount, "Expected current row count to be 1 after processing a single row");
                Assert.AreEqual<int>(0, bufferService.RowCount, "Expected standard row count to be 0 after processing a single row with an exception thrown");
                Assert.AreEqual<int>(1, bufferService.ErrorRowCount, "Expected error row count to be 1 after processing a single row with an exception thrown");
                Assert.AreEqual<int>(0, bufferService.LogRowCount, "Expected log row count to be 0 after processing a single row with an exception thrown");

                throw;
            }
        }

        [TestMethod()]
        public void AddRow_HandleRowAndForceBufferException_ReturnExpectedRowCounts()
        {
            OutputTestImpl output = null;
            ComponentBufferServiceTestImpl bufferService = null;
            GenerateOutputColumns(new string[] { "A", "B", "C", "D" }, true, false, DTSRowDisposition.RD_FailComponent, DTSRowDisposition.RD_FailComponent, out output, out bufferService);
            bufferService.ExceptionToFire = new DoesNotFitBufferException();
            BufferSink target = new BufferSink(bufferService, output, true, false, false);
            SetOutputColumns(target, bufferService.ColumnCount);
            // mock some initial rows
            target.CurrentRowCount = 3;
            string[] data = new string[] { "1", "", "3", "4" };

            RowData rowData = GenerateRowData(data);
            
            target.AddRow(rowData);

            Assert.AreEqual<Int64>(4, target.CurrentRowCount, "Expected current row count to be 4 after processing 4 mock rows");
            Assert.AreEqual<int>(0, bufferService.RowCount, "Expected standard row count to be 0 after processing a single row with an allowable exception thrown");
            Assert.AreEqual<int>(1, bufferService.ErrorRowCount, "Expected error row count to be 1 after processing a single row with an allowable exception thrown");
            Assert.AreEqual<int>(0, bufferService.LogRowCount, "Expected log row count to be 0 after processing a single row with an allowable exception thrown");
        }

        [TestMethod()]
        public void AddRow_HandleGoodAndBadRowsAndForceBufferException_ReturnExpectedRowDataAndCounts()
        {
            OutputTestImpl output = null;
            ComponentBufferServiceTestImpl bufferService = null;
            GenerateOutputColumns(new string[] { "A", "B", "C", "D" }, true, false, DTSRowDisposition.RD_FailComponent, DTSRowDisposition.RD_RedirectRow, out output, out bufferService);
            BufferSink target = new BufferSink(bufferService, output, true, false, false);
            SetOutputColumns(target, bufferService.ColumnCount);
            // mock some initial rows
            target.CurrentRowCount = 3;
            string[] data = new string[] { "1", "", "3", "4" };

            RowData rowData = GenerateRowData(data);
            target.AddRow(rowData);
            Assert.AreEqual<Int64>(4, target.CurrentRowCount, "Expected current row count to be 4 after processing 4 mock rows");
            Assert.AreEqual<int>(1, bufferService.RowCount, "Expected standard row count to be 1 after processing a single valid row");
            Assert.AreEqual<int>(0, bufferService.ErrorRowCount, "Expected error row count to be 0 after processing a single valid row");
            Assert.AreEqual<int>(0, bufferService.LogRowCount, "Expected log row count to be 0 after processing a single valid row with no log redirect");

            VerifyAddedRowData(bufferService, new object[] { "1", null, "3", "4" });

            bufferService.ExceptionToFire = new OverflowException();

            target.AddRow(rowData);
            Assert.AreEqual<Int64>(5, target.CurrentRowCount, "Expected current row count to be 5 after processing 5 mock rows");
            Assert.AreEqual<int>(1, bufferService.RowCount, "Expected standard row count to be 1 after processing a single valid row");
            Assert.AreEqual<int>(1, bufferService.ErrorRowCount, "Expected error row count to be 1 after processing a single invalid row");
            Assert.AreEqual<int>(0, bufferService.LogRowCount, "Expected log row count to be 0 after processing a single valid row with no log redirect");
        }

        [TestMethod()]
        public void AddRow_HandleRowAndForceExceptionWithErrorIgnore_ReturnExpectedRowCounts()
        {
            OutputTestImpl output = null;
            ComponentBufferServiceTestImpl bufferService = null;
            GenerateOutputColumns(new string[] { "A", "B", "C", "D" }, true, false, DTSRowDisposition.RD_IgnoreFailure, DTSRowDisposition.RD_RedirectRow, out output, out bufferService);
            bufferService.ExceptionToFire = new ArgumentException();
            BufferSink target = new BufferSink(bufferService, output, true, true, false);
            SetOutputColumns(target, bufferService.ColumnCount);
            // mock some initial rows
            target.CurrentRowCount = 3;
            string[] data = new string[] { "1", "", "3", "4" };

            RowData rowData = GenerateRowData(data);
            target.AddRow(rowData);
            Assert.AreEqual<Int64>(4, target.CurrentRowCount, "Expected current row count to be 4 after processing 4 mock rows");
            Assert.AreEqual<int>(0, bufferService.RowCount, "Expected standard row count to be 0 after processing 1 invalid row");
            Assert.AreEqual<int>(0, bufferService.ErrorRowCount, "Expected error row count to be 0 after processing 1 invalid row with error handling set to ignore");
            Assert.AreEqual<int>(0, bufferService.LogRowCount, "Expected log row count to be 0 after processing 1 invalid row with error handling set to ignore");
        }

        [TestMethod()]
        public void AddRow_HandleRowAndForceBufferExceptionWithErrorAuditing1_ReturnExpectedRowCounts()
        {
            OutputTestImpl output = null;
            ComponentBufferServiceTestImpl bufferService = null;
            GenerateOutputColumns(new string[] { "A", "B", "C", "D" }, true, false, DTSRowDisposition.RD_RedirectRow, DTSRowDisposition.RD_RedirectRow, out output, out bufferService);
            bufferService.ExceptionToFire = new FormatException();
            BufferSink target = new BufferSink(bufferService, output, true, true, false);
            SetOutputColumns(target, bufferService.ColumnCount);
            // mock some initial rows
            target.CurrentRowCount = 3;

            // empty values will be treated as nulls and bypass the exception-causing SetColumnData
            string[] data = new string[] { "1", "", "3", "4" };

            RowData rowData = GenerateRowData(data);
            target.AddRow(rowData);
            Assert.AreEqual<Int64>(4, target.CurrentRowCount, "Expected current row count to be 4 after processing 4 mock rows");
            Assert.AreEqual<int>(0, bufferService.RowCount, "Expected standard row count to be 0 after processing 1 invalid row");
            Assert.AreEqual<int>(3, bufferService.ErrorRowCount, "Expected error row count to be 3 after processing 1 invalid row with error handling set to redirect and audit");
            Assert.AreEqual<int>(0, bufferService.LogRowCount, "Expected log row count to be 0 after processing 1 invalid row with no log handling");
        }

        [TestMethod()]
        public void AddRow_HandleRowAndForceBufferExceptionWithErrorAuditing2_ReturnExpectedRowCounts()
        {
            OutputTestImpl output = null;
            ComponentBufferServiceTestImpl bufferService = null;
            GenerateOutputColumns(new string[] { "A", "B", "C", "D" }, true, false, DTSRowDisposition.RD_RedirectRow, DTSRowDisposition.RD_RedirectRow, out output, out bufferService);
            bufferService.ExceptionToFire = new FormatException();
            BufferSink target = new BufferSink(bufferService, output, true, true, false);
            SetOutputColumns(target, bufferService.ColumnCount);
            // mock some initial rows
            target.CurrentRowCount = 3;

            // empty values will be treated as nulls and bypass the exception-causing SetColumnData
            string[] data = new string[] { "1", "2", "", "" };

            RowData rowData = GenerateRowData(data);
            target.AddRow(rowData);
            Assert.AreEqual<Int64>(4, target.CurrentRowCount, "Expected current row count to be 4 after processing 4 mock rows");
            Assert.AreEqual<int>(0, bufferService.RowCount, "Expected standard row count to be 0 after processing 1 invalid row");
            Assert.AreEqual<int>(2, bufferService.ErrorRowCount, "Expected error row count to be 2 after processing 1 invalid row with error handling set to redirect and audit");
            Assert.AreEqual<int>(0, bufferService.LogRowCount, "Expected log row count to be 0 after processing 1 invalid row with no log handling");
        }

        [TestMethod()]
        public void AddRow_HandleRowAndForceBufferExceptionWithLogging1_ReturnExpectedRowCounts()
        {
            OutputTestImpl output = null;
            ComponentBufferServiceTestImpl bufferService = null;
            GenerateOutputColumns(new string[] { "A", "B", "C", "D" }, true, true, DTSRowDisposition.RD_IgnoreFailure, DTSRowDisposition.RD_IgnoreFailure, out output, out bufferService);
            bufferService.ExceptionToFire = new FormatException();
            BufferSink target = new BufferSink(bufferService, output, true, true, false);
            SetOutputColumns(target, bufferService.ColumnCount);
            // mock some initial rows
            target.CurrentRowCount = 3;

            // empty values will be treated as nulls and bypass the exception-causing SetColumnData
            string[] data = new string[] { "1", "", "3", "4" };

            RowData rowData = GenerateRowData(data);
            target.AddRow(rowData);
            Assert.AreEqual<Int64>(4, target.CurrentRowCount, "Expected current row count to be 4 after processing 4 mock rows");
            Assert.AreEqual<int>(1, bufferService.RowCount, "Expected standard row count to be 1 after processing 1 invalid row with errors set to ignore");
            Assert.AreEqual<int>(0, bufferService.ErrorRowCount, "Expected error row count to be 0 after processing 1 invalid row with no error handling");
            Assert.AreEqual<int>(3, bufferService.LogRowCount, "Expected log row count to be 3 after processing 1 invalid row with log handling");
        }

        [TestMethod()]
        public void AddRow_HandleRowAndForceBufferExceptionWithLogging2_ReturnExpectedRowCounts()
        {
            OutputTestImpl output = null;
            ComponentBufferServiceTestImpl bufferService = null;
            GenerateOutputColumns(new string[] { "A", "B", "C", "D" }, true, true, DTSRowDisposition.RD_IgnoreFailure, DTSRowDisposition.RD_IgnoreFailure, out output, out bufferService);
            bufferService.ExceptionToFire = new FormatException();
            BufferSink target = new BufferSink(bufferService, output, true, true, false);
            SetOutputColumns(target, bufferService.ColumnCount);
            // mock some initial rows
            target.CurrentRowCount = 3;

            // empty values will be treated as nulls and bypass the exception-causing SetColumnData
            string[] data = new string[] { "1", "", "", "" };

            RowData rowData = GenerateRowData(data);
            target.AddRow(rowData);
            Assert.AreEqual<Int64>(4, target.CurrentRowCount, "Expected current row count to be 4 after processing 4 mock rows");
            Assert.AreEqual<int>(1, bufferService.RowCount, "Expected standard row count to be 1 after processing 1 invalid row with errors set to ignore");
            Assert.AreEqual<int>(0, bufferService.ErrorRowCount, "Expected error row count to be 0 after processing 1 invalid row with no error handling");
            Assert.AreEqual<int>(1, bufferService.LogRowCount, "Expected log row count to be 1 after processing 1 invalid row with log handling");
        }

        [TestMethod()]
        public void AddRow_HandleRowsAndForceBufferExceptionWithLoggingAndErrorAuditing_ReturnExpectedRowCounts()
        {
            OutputTestImpl output = null;
            ComponentBufferServiceTestImpl bufferService = null;
            GenerateOutputColumns(new string[] { "A", "B", "C", "D" }, true, true, DTSRowDisposition.RD_RedirectRow, DTSRowDisposition.RD_IgnoreFailure, out output, out bufferService);
            bufferService.ExceptionToFire = new OverflowException();
            BufferSink target = new BufferSink(bufferService, output, true, true, false);
            SetOutputColumns(target, bufferService.ColumnCount);
            // mock some initial rows
            target.CurrentRowCount = 3;

            // empty values will be treated as nulls and bypass the exception-causing SetColumnData
            string[] data = new string[] { "1", "", "3", "4" };

            RowData rowData = GenerateRowData(data);
            target.AddRow(rowData);
            Assert.AreEqual<Int64>(4, target.CurrentRowCount, "Expected current row count to be 4 after processing 4 mock rows");
            Assert.AreEqual<int>(1, bufferService.RowCount, "Expected standard row count to be 1 after processing 1 invalid row with truncation errors set to ignore");
            Assert.AreEqual<int>(0, bufferService.ErrorRowCount, "Expected error row count to be 0 after processing 1 invalid truncation row");
            Assert.AreEqual<int>(3, bufferService.LogRowCount, "Expected log row count to be 3 after processing 1 invalid row with log handling");

            bufferService.ExceptionToFire = new ArgumentException();
            try
            {
                target.AddRow(rowData);
                Assert.Fail("Expected ArgumentException was not thrown");
            }
            catch
            {
                Assert.AreEqual<Int64>(5, target.CurrentRowCount, "Expected current row count to be 5 after processing 5 mock rows");
                Assert.AreEqual<int>(1, bufferService.RowCount, "Expected standard row count to be 1 after processing 2 invalid rows with truncation errors set to ignore");
                Assert.AreEqual<int>(3, bufferService.ErrorRowCount, "Expected error row count to be 3 after processing 2 invalid row with error handling");
                Assert.AreEqual<int>(3, bufferService.LogRowCount, "Expected log row count to be 3 after processing 2 invalid row with log handling");
            }
        }

        [TestMethod()]
        public void AddRow_HandleRowAndForceBufferExceptionWithNoLoggingWithTruncationIgnore_ReturnExpectedRowCounts()
        {
            OutputTestImpl output = null;
            ComponentBufferServiceTestImpl bufferService = null;
            GenerateOutputColumns(new string[] { "A", "B", "C", "D" }, true, false, DTSRowDisposition.RD_IgnoreFailure, DTSRowDisposition.RD_IgnoreFailure, out output, out bufferService);
            bufferService.ExceptionToFire = new OverflowException();
            BufferSink target = new BufferSink(bufferService, output, true, true, false);
            SetOutputColumns(target, bufferService.ColumnCount);
            // mock some initial rows
            target.CurrentRowCount = 3;

            // empty values will be treated as nulls and bypass the exception-causing SetColumnData
            string[] data = new string[] { "1", "", "3", "4" };

            RowData rowData = GenerateRowData(data);
            target.AddRow(rowData);
            Assert.AreEqual<Int64>(4, target.CurrentRowCount, "Expected current row count to be 4 after processing 4 mock rows");
            Assert.AreEqual<int>(1, bufferService.RowCount, "Expected standard row count to be 1 after processing 1 invalid row with truncation errors set to ignore");
            Assert.AreEqual<int>(0, bufferService.ErrorRowCount, "Expected error row count to be 0 after processing 1 invalid row with no error handling");
            Assert.AreEqual<int>(0, bufferService.LogRowCount, "Expected log row count to be 0 after processing 1 invalid row with no log handling");
        }

        /// <summary>
        /// Generates a set of output columns using the settings provided and adds 
        /// them to the OutputTestImpl output
        /// </summary>
        /// <param name="columnNames">The list of column names to generate</param>
        /// <param name="errorBufferUsed">Indicates if the error buffer will be used</param>
        /// <param name="errorDisposition">The error disposition setting the columns should have</param>
        /// <param name="truncationDisposition">The truncation disposition setting the columns should have</param>
        /// <param name="output">The output which will be initialized and have columns added to</param>
        /// <param name="bufferService">The buffer service which will be initialized based on the column names and error/log buffers being used</param>
        private void GenerateOutputColumns(string[] columnNames, bool errorBufferUsed, bool logBufferUsed, 
            DTSRowDisposition errorDisposition, DTSRowDisposition truncationDisposition, out OutputTestImpl output, out ComponentBufferServiceTestImpl bufferService)
        {
            output = new OutputTestImpl();
            bufferService = new ComponentBufferServiceTestImpl(columnNames, errorBufferUsed, logBufferUsed);

            int currentID = 1;

            foreach (string columnName in columnNames)
            {
                IDTSOutputColumn100 outputColumn = output.OutputColumnCollection.New();
                outputColumn.ID = currentID;
                outputColumn.Name = columnName;
                outputColumn.ErrorRowDisposition = errorDisposition;
                outputColumn.TruncationRowDisposition = truncationDisposition;
                outputColumn.SetDataTypeProperties(DataType.DT_STR, 255, 0, 0, 0);
                currentID++;
            }
        }

        /// <summary>
        /// Generates a RowData object using a list of column values
        /// </summary>
        /// <param name="columnData">The list of column values to generate the row data object from</param>
        /// <returns>The generated RowData object</returns>
        private RowData GenerateRowData(string[] columnData)
        {
            RowData rowData = new RowData();
            foreach (string columnValue in columnData)
            {
                rowData.AddColumnData(columnValue);
            }

            return rowData;
        }

        /// <summary>
        /// Verifies that the current row data within the buffer service matches
        /// the list of column data provided
        /// </summary>
        /// <param name="bufferService">The buffer service to inspect the current row</param>
        /// <param name="data">The list of expected column values</param>
        private void VerifyAddedRowData(ComponentBufferServiceTestImpl bufferService, object[] data)
        {
            for (int i = 0; i < bufferService.ColumnCount; i++)
            {
                if (i < data.Length)
                {
                    Assert.AreEqual(data[i], bufferService.GetColumnData(i), "Expected column data did not match actual column data");
                }
                else
                {
                    Assert.AreEqual(null, bufferService.GetColumnData(i), "Expected column data was not equal to null based on the column being out of range");
                }
            }
        }

        /// <summary>
        /// Configures the buffer sink to use all available file
        /// columns as output columns
        /// </summary>
        /// <param name="sink">The buffer sink to configure</param>
        /// <param name="columns">The number of columns to mark as outputs</param>
        private void SetOutputColumns(BufferSink sink, int columns)
        {
            var configuredColumns = new List<ConfiguredFileColumn>();

            for (int colIndex = 0; colIndex < columns; colIndex++)
            {
                configuredColumns.Add(new ConfiguredFileColumn()
                    {
                        IsOutput = true
                    });
            }

            sink.DefineStandardOutputColumns(configuredColumns);
        }
    }
}
